﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using DDD.Core;


namespace DDD.OpenTK {
    /// <summary>
    /// <see cref="FeedbackBuffer"/>クラスに拡張メソッドを追加するクラス
    /// </summary>
    public static class FeedbackBufferExtensions {

        /// <summary>
        /// コンパイル
        /// </summary>
        /// <param name="fbuf"><see cref="FeedbackBuffer"/>オブジェクト</param>
        /// <param name="sp"><see cref="ShaderProgram"/>オブジェクト</param>
        public static void Compile (this FeedbackBuffer fbuf, ShaderProgram sp) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[fbuf];
            if (id > 0) {
                return;
            }

            foreach (var varry in fbuf.VertexArrays) {
                varry.Compile ();
            }

            ErrorCode err;

            GL.GenTransformFeedback (1, out id);
            if ((err = GL.GetError ()) != 0) {
                throw new InvalidOperationException ("glGenTransformFeedback was failed, err=" + err);
            }

            GL.BindTransformFeedback (TransformFeedbackTarget.TransformFeedback, id);

            for (var i = 0; i < sp.FeedbackVaryingCount; i++) {
                var name = sp.GetFeedbackVarying (i);
                var varry = fbuf.GetFeedbackVarying (name).VertexArray;
                if (varry == null) {
                    throw new InvalidOperationException ("Declared feedback varying in shader is not registered in FeedbackBuffer");
                }
                GL.BindBufferBase (BufferTarget.TransformFeedbackBuffer, i, cache[varry]);
                if ((err = GL.GetError ()) != 0) {
                    throw new InvalidOperationException ("glBindBufferBase was failed, err=" + err);
                }
            }

            GL.BindTransformFeedback (TransformFeedbackTarget.TransformFeedback, 0);
            cache.Add (fbuf, id);
        }


        /// <summary>
        /// レンダリング
        /// </summary>
        /// <param name="fbuf"><see cref="FeedbackBuffer"/>オブジェクト</param>
        public static void Render (this FeedbackBuffer fbuf) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[fbuf];
            if (id == 0) {
                throw new InvalidOperationException ("OpenGL id was not found");
            }

            GL.BindTransformFeedback (TransformFeedbackTarget.TransformFeedback, id);
        }

        /// <summary>
        /// ポストレンダリング処理
        /// </summary>
        /// <param name="fbuf">フィードバックバッファー</param>
        public static void PostRender (this FeedbackBuffer fbuf) {
            var cache = Graphics3DExtensions.GetGLCache ();
            ErrorCode err;

            foreach (var varying in fbuf.FeedbackVaryings) {
                var src = varying.VertexArray;
                var srcID = cache[src];
                if (srcID == 0) {
                    throw new InvalidOperationException ("OpenGL id was not found");
                }
                var dst = varying.CopyTo;
                if (dst != null) {
                    var dstID = cache[dst];
                    if (dstID == 0) {
                        throw new InvalidOperationException ("OpenGL id was not found");
                    }

                    GL.BindBuffer (BufferTarget.CopyReadBuffer, srcID);
                    GL.BindBuffer (BufferTarget.CopyWriteBuffer, dstID);

                    var size = 0;
                    GL.GetBufferParameter (BufferTarget.CopyReadBuffer, BufferParameterName.BufferSize, out size);
                    GL.CopyBufferSubData (BufferTarget.CopyReadBuffer, BufferTarget.CopyWriteBuffer, (IntPtr)0, (IntPtr)0, (IntPtr)size);
                    if ((err = GL.GetError ()) != 0) {
                        throw new InvalidOperationException ("OpenGL was not found, err=" + err);
                    }
                }
            }

            GL.BindBuffer (BufferTarget.CopyReadBuffer, 0);
            GL.BindBuffer (BufferTarget.CopyWriteBuffer, 0);
        }
    }
}
